#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/poll.h>
#include <linux/sched/signal.h>
#include <linux/slab.h>
#include <linux/uaccess.h>

#define GLOBALFIFO_SIZE 0x1000
#define FIFO_CLEAR 0x1
#define GLOBALFIFO_MAJOR 230

static int globalfifo_major = GLOBALFIFO_MAJOR;
module_param(globalfifo_major, int, S_IRUGO);

struct globalfifo_dev {
  struct cdev cdev;
  unsigned int current_len;            // 表示当前FIFO的有效长度
  unsigned char mem[GLOBALFIFO_SIZE];  // 内核中的一部分内存
  struct mutex mutex;
  wait_queue_head_t
      r_wait;  // 在设备文件为空的时候读设备，导致进程阻塞，把此进程加入到r_wait队列中，等待唤醒
  wait_queue_head_t
      w_wait;  // 在设备文件写满的时候继续写设备，导致写级才能拿阻塞，把此进程插入w_wait队列中，等待读进程将其唤醒

};  // 这些数据不一定放在一个结构中

struct globalfifo_dev *globalfifo_devp;

static int globalfifo_open(struct inode *inode, struct file *filp) {
  printk(KERN_INFO "into open\n");
  filp->private_data = globalfifo_devp;
  return 0;
}

static int globalfifo_release(struct inode *inode, struct file *filp) {
  return 0;
}
/*

    ppos:是文件相对于文件开头的偏移
*/
static ssize_t globalfifo_read(struct file *filp, char __user *buf,
                               size_t count, loff_t *ppos) {
  int ret;
  struct globalfifo_dev *dev = (struct globalfifo_dev *)filp->private_data;
  DECLARE_WAITQUEUE(wait, current);
  // 把当前进程加入到wait 这个等待队列的结构体中，

  mutex_lock(&(dev->mutex));
  add_wait_queue(&(dev->r_wait), &wait);
  // 把wait元素插入r_wait等待队列中；此时当前进程还未真正睡眠

  while (dev->current_len == 0) {  // globalfifo没有数据，读数据导致阻塞
    if (filp->f_flags & O_NONBLOCK) {  // 非阻塞的方式，直接返回错误码
      ret = -EAGAIN;
      goto out;
    }
    __set_current_state(
        TASK_UNINTERRUPTIBLE);  // 对于阻塞读的进程，设置当前进程的状态为睡眠
    mutex_unlock(&(dev->mutex));

    schedule();  // 然后一个调度，直接切进程
    if (signal_pending(current)) {
      ret = -ERESTARTSYS;
      goto out2;
    }

    mutex_lock(&(dev->mutex));
  }

  /*
      正常情况到达此处，说明设备文件可以读了。
      既然能读了(读完的数据会被清除)，说明也能写了,
     应该唤醒写进程(注意是在读完之后唤醒写进程)
  */
  if (count > dev->current_len) {
    count = dev->current_len;
  }
  if (copy_to_user(buf, dev->mem,
                   count)) {  // 从内核拷贝文件到用户态，如果出错，返回错误码
    ret = -EFAULT;
    goto out;
  } else {  // 拷贝成功(读成功)的话，说明此时设备文件一定不为满。即肯定是可写入的，因此唤醒写设备进程
    memcpy(dev->mem, dev->mem + count, dev->current_len - count);
    dev->current_len -= count;
    printk(KERN_INFO "read %ld byte(s), current_len:%d\n", count,
           dev->current_len);
    wake_up_interruptible(&(dev->w_wait));  // 唤醒w_wait等待队列中的阻塞进程
    ret = count;
  }
out:
  mutex_unlock(&(dev->mutex));
out2:
  remove_wait_queue(&(dev->w_wait), &wait);
  set_current_state(
      TASK_RUNNING);  // 能执行到这儿，这个进程可能是从阻塞状态唤醒的，设置进程状态为可运行
  return ret;
}

static ssize_t globalfifo_write(struct file *filp, const char __user *buf,
                                size_t count, loff_t *ppos) {
  struct globalfifo_dev *dev = (struct globalfifo_dev *)filp->private_data;
  int ret;
  DECLARE_WAITQUEUE(wait, current);

  mutex_lock(&(dev->mutex));
  add_wait_queue(&(dev->w_wait), &wait);  //同样的方式初始化w_wait队列

  while (dev->current_len == GLOBALFIFO_SIZE) {  // 如果设备文件满
    if (filp->f_flags & O_NONBLOCK) {  // 如果是非阻塞状态的，直接返回，
      ret = -EAGAIN;
      goto out;
    }
    __set_current_state(
        TASK_UNINTERRUPTIBLE);  // 阻塞式IO，设置当前进程为阻塞状态，此时线程还未真正睡眠
    mutex_unlock(&(dev->mutex));
    schedule();  // 重新调度使得此进程睡眠

    if (signal_pending(current)) {
      ret = -ERESTARTSYS;
      goto out2;
    }

    mutex_lock(&(dev->mutex));
  }

  /**
   * @brief 出循环说明该进程已经可以写设备文件了。
   * 既然能写了，说明也能读了，因此唤醒相应的读设备进程(注意是在当前的谢金成写完数据之后唤醒读进程)
   */

  if (count > GLOBALFIFO_SIZE - dev->current_len)
    count = GLOBALFIFO_SIZE - dev->current_len;
  if (copy_from_user(dev->mem + dev->current_len, buf, count)) {
    ret = -EFAULT;
    goto out;
  } else {  // 写成功，说明此时设备文件肯定不为空，唤醒读设备文件进程
    dev->current_len += count;
    printk(KERN_INFO "written %ld byte(s), current_len:%d\n", count,
           dev->current_len);
    //唤醒dev->r_wait等待队列，遍历等待队列，唤醒队列中所有进程
    // 此函数是可以被信号唤醒，因此在进程被唤醒之后，如果是因为信号被唤醒，需要再次休眠
    wake_up_interruptible(&(dev->r_wait));
    ret = count;
  }

out:
  mutex_unlock(&(dev->mutex));
out2:
  remove_wait_queue(&(dev->w_wait), &wait);
  set_current_state(TASK_RUNNING);
  return ret;
}

static loff_t globalfifo_llseek(struct file *filp, loff_t offset, int orig) {
  loff_t ret = 0;
  switch (orig) {
    case 0:
      if (offset < 0) {
        ret = -EINVAL;
        break;
      }
      if ((unsigned int)offset > GLOBALFIFO_SIZE) {
        ret = -EINVAL;
        break;
      }
      filp->f_pos = (unsigned int)offset;
      ret = filp->f_pos;
      break;
    case 1:
      if ((filp->f_pos + offset) > GLOBALFIFO_SIZE) {
        ret = -EINVAL;
        break;
      }
      if ((filp->f_pos + offset) < 0) {
        ret = -EINVAL;
        break;
      }
      filp->f_pos += offset;
      ret = filp->f_pos;
      break;
    default:
      ret = -EINVAL;
      break;
  }
  return ret;
}

static long globalfifo_ioctl(struct file *filp, unsigned int cmd,
                             unsigned long arg) {
  struct globalfifo_dev *dev = filp->private_data;
  switch (cmd) {
    case FIFO_CLEAR:
      mutex_lock(&dev->mutex);
      dev->current_len = 0;
      memset(dev->mem, 0, GLOBALFIFO_SIZE);
      mutex_unlock(&dev->mutex);

      printk(KERN_INFO "globalmem is set to  zero\n");
      break;
    default:
      return -EINVAL;
  }
  return 0;
}

static unsigned int globalfifo_poll(struct file *filp, poll_table *wait) {
  unsigned int mask;
  struct globalfifo_dev *dev = filp->private_data;

  mutex_lock(&(dev->mutex));

  poll_wait(filp, &(dev->r_wait), wait);
  poll_wait(filp, &(dev->w_wait), wait);

  if (dev->current_len != 0) {
    mask |= POLLIN | POLLRDNORM;  // 状态可读
  }
  if (dev->current_len != GLOBALFIFO_SIZE) {
    mask |= POLLOUT | POLLWRNORM;  // 状态可写
  }
  mutex_unlock(&(dev->mutex));
  return mask;
}

static const struct file_operations globalfifo_fops = {
    .owner = THIS_MODULE,
    .llseek = globalfifo_llseek,
    .read = globalfifo_read,
    .write = globalfifo_write,
    .unlocked_ioctl = globalfifo_ioctl,
    .open = globalfifo_open,
    .release = globalfifo_release,
    .poll = globalfifo_poll,
};

// 完成cdev的初始化和添加
static void globalfifo_setup_cdev(struct globalfifo_dev *dev, int index) {
  int err, devno = MKDEV(globalfifo_major, index);
  // MKDEV:根据设备文件的major，minor号找到设备在设备表中的位置， index作为minor
  // 系统设备在`/proc/devices`中，格式为(设备编号，设备类型名)

  // 此函数会将cdev结构体包含双向链表list，此函数把cdev结构体加入到链表中；并且将fops地址传递给cdev的fops成员。
  cdev_init(&dev->cdev, &globalfifo_fops);

  dev->cdev.owner = THIS_MODULE;
  err = cdev_add(&dev->cdev, devno, 1);
  // `cdev_add`将设备注册到kernel，
  if (err) {
    printk(KERN_NOTICE "Error %d adding globalmem%d\n", err, index);
  }
}

static int __init globalfifo_init(void) {
  int ret;
  dev_t devno = MKDEV(globalfifo_major, 0);
  // `MKDEV`在此处直接确定了一个设备号
  if (globalfifo_major) {
    ret = register_chrdev_region(devno, 1, "globalmem");
    // 如果事先定义设备编号，就直接申请这个设备编号
  } else {
    ret = alloc_chrdev_region(&devno, 0, 1, "globalmem");
    // 如果实现没有定义设备编号，动态分配设备编号(ps.一些设备编号是静态分配给普通设备的)
    globalfifo_major = MAJOR(devno);
  }
  // 申请事先定义的设备编号出错，module加载失败
  if (ret < 0) {
    return ret;
  }

  // 申请globalfifo_dev结构体内存并置为0
  globalfifo_devp = kzalloc(sizeof(struct globalfifo_dev), GFP_KERNEL);
  if (!globalfifo_devp) {
    ret = -ENOMEM;
    goto fail_malloc;
  }

  globalfifo_setup_cdev(globalfifo_devp, 0);

  mutex_init(&globalfifo_devp->mutex);
  init_waitqueue_head(&globalfifo_devp->r_wait);  // 初始化r_wait等待队列
  init_waitqueue_head(&globalfifo_devp->w_wait);  // 初始化w_wait等待队列

  return 0;

fail_malloc:
  unregister_chrdev_region(devno, 1);
  return ret;
}

static void __exit globalfifo_exit(void) {
  cdev_del(&globalfifo_devp->cdev);
  kfree(globalfifo_devp->mem);
  unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1);
}

module_init(globalfifo_init);
module_exit(globalfifo_exit);

MODULE_AUTHOR("shang-chao");
MODULE_LICENSE("GPL v2");
